package com.eyealike.client.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.validation.Schema;

import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;

public class HttpClientMarshaller
{
	private final JAXBContext context;
	private final ValidationEventHandler handler;
	private final ResourcePool<Marshaller> marshallerPool;
	private final ResourcePool<Unmarshaller> unmarshallerPool;

	public HttpClientMarshaller( String bindingPackages )
	{
		this( bindingPackages, false );
	}

	public HttpClientMarshaller( String bindingPackages, boolean pooling )
	{
		handler = new Handler();
		try {
			context = JAXBContext.newInstance( bindingPackages );
		} catch( final JAXBException e ) {
			throw new RuntimeException( e );
		}
		if( pooling ) {
			marshallerPool = new ResourcePool<Marshaller>( 20 )
			{
				@Override
				protected Marshaller allocate()
				{
					return newMarshaller();
				}

				@Override
				protected void deallocate( Marshaller resource )
				{
					// nothing to do
				}
			};
			unmarshallerPool = new ResourcePool<Unmarshaller>( 20 )
			{
				@Override
				protected Unmarshaller allocate()
				{
					return newUnmarshaller();
				}

				@Override
				protected void deallocate( Unmarshaller resource )
				{
					// nothing to do
				}
			};
		} else {
			marshallerPool = null;
			unmarshallerPool = null;
		}
	}

	private Marshaller getMarshaller()
	{
		return marshallerPool == null ? newMarshaller() : marshallerPool.acquire();
	}

	private Unmarshaller getUnmarshaller()
	{
		return unmarshallerPool == null ? newUnmarshaller() : unmarshallerPool.acquire();
	}

	private Marshaller newMarshaller()
	{
		Marshaller marshaller;
		try {
			marshaller = context.createMarshaller();
			marshaller.setSchema( getSchema() );
			marshaller.setEventHandler( handler );
			return marshaller;
		} catch( JAXBException e ) {
			throw new RuntimeException( e );
		}
	}

	private Unmarshaller newUnmarshaller()
	{
		Unmarshaller unmarshaller;
		try {
			unmarshaller = context.createUnmarshaller();
			unmarshaller.setSchema( getSchema() );
			unmarshaller.setEventHandler( handler );
			return unmarshaller;
		} catch( JAXBException e ) {
			throw new RuntimeException( e );
		}
	}

	private void releaseMarshaller( Marshaller marshaller )
	{
		if( marshallerPool != null ) marshallerPool.release( marshaller );
	}

	private void releaseUnarshaller( Unmarshaller unmarshaller )
	{
		if( unmarshallerPool != null ) unmarshallerPool.release( unmarshaller );
	}

	protected Schema getSchema()
	{
		return null;
	}

	public void marshal( Object body, final OutputStream stream )
	{
		final Marshaller marshaller = getMarshaller();
		try {
			marshaller.marshal( body, stream );
		} catch( final JAXBException e ) {
			throw new RuntimeException( e );
		} finally {
			releaseMarshaller( marshaller );
		}
	}

	public <D> D unmarshal( final InputStream stream, Class<D> clazz )
	{
		final Unmarshaller unmarshaller = getUnmarshaller();
		try {
			return cast( clazz, unmarshaller.unmarshal( stream ) );
		} catch( final JAXBException e ) {
			throw new RuntimeException( e );
		} finally {
			releaseUnarshaller( unmarshaller );
		}
	}

	public void marshal( Object body, EntityEnclosingMethod method )
	{
		final ByteArrayOutputStream stream = new ByteArrayOutputStream();
		marshal( body, stream );
		method.setRequestEntity( new ByteArrayRequestEntity( stream.toByteArray() ) );
	}

	public <D> D unmarshal( HttpMethod method, Class<D> clazz ) throws IOException
	{
		final InputStream stream = method.getResponseBodyAsStream();
		return unmarshal( stream, clazz );
	}

	@SuppressWarnings( "unchecked" )
	private <D> D cast( Class<D> clazz, final Object object )
	{
		return clazz == null ? (D) object : clazz.cast( object );
	}

	private class Handler implements ValidationEventHandler
	{
		@Override
		public boolean handleEvent( ValidationEvent event )
		{
			return false;
		}
	}
}
